﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Reflection;
using System.Collections;
using Jacaranda.Rules;

namespace Jacaranda.Model
{
    public static class Metadata
    {
        public static EntityMetadata Type(Type type)
        {
            return types[type];
        }
    


        public static List<EntityAssociation> Associations { get; private set; }
        public static List<EntityMetadata> RegisteredTypes { get; private set; }

        private static Dictionary<Type, EntityMetadata> types;

        public static void RegisterAssembly(Assembly assembly)
        {
            List<Type> entityTypes = assembly.GetTypes()
                    .Where(i => i.GetCustomAttributes(typeof(TableAttribute), true).Length > 0 || i.GetCustomAttributes(typeof(BaseClassAttribute), true).Length > 0)
                    .ToList();


            entityTypes.ForEach(i => RegisterType(i));
            entityTypes.ForEach(i => AddAssociationFields(i));
            entityTypes.ForEach(i => AddAssociations(i));
            entityTypes.ForEach(i => AddSubTypes(i));
        }

        internal static void ClearMetadata()
        {
            RegisteredTypes = new List<EntityMetadata>();
            Associations = new List<EntityAssociation>();
            types = new Dictionary<Type, EntityMetadata>();
        }

        private static void AddSubTypes(Type type)
        {
            EntityMetadata metadata = RegisteredTypes.First(i => i.Type.Equals(type));

            if (metadata.IsTable)
            {
                return;
            }


            List<EntityMetadata> subTypes = new List<EntityMetadata>();

            foreach (EntityMetadata m in RegisteredTypes.Where(i => i != metadata))
            {
                if (metadata.Type.IsAssignableFrom(m.Type))
                {
                    subTypes.Add(m);
                }

                m.BaseType = metadata;
            }

            metadata.SubTypes = subTypes.ToArray();
        }

        private static void AddAssociations(Type type)
        {
            List<EntityAssociation> associations = new List<EntityAssociation>();

            foreach (EntityAssociation association in Associations)
            {
                if (association.From.Equals(type) || association.To.Equals(type))
                {
                    associations.Add(association);
                }
            }

            Type(type).Associations = associations.ToArray();
        }

        private static void AddAssociationFields(Type type)
        {
            List<AssociationField> associationFields = new List<AssociationField>();

            foreach (PropertyInfo property in type.GetProperties().Where(i => i.DeclaringType.Equals(type) && i.GetCustomAttributes(typeof(AssociationAttribute), false).Length > 0))
            {
                AssociationField field = new AssociationField()
                {
                    AssociationType = typeof(EntityList).IsAssignableFrom(property.PropertyType) ? AssociationType.OneToMany : AssociationType.OneToOne,
                    PropertyName = property.Name,
                    From = type,
                    To = typeof(EntityList).IsAssignableFrom(property.PropertyType) ? property.PropertyType.GetGenericArguments()[0] : property.PropertyType,
                };

                int instance = (property.GetCustomAttributes(typeof(AssociationAttribute), true)[0] as AssociationAttribute).Instance;


                bool isSourceToTargetOrder = true;
                EntityAssociation association = Associations.FirstOrDefault(i => field.From.IsAssignableFrom(i.From) && field.To.IsAssignableFrom(i.To));
                
                if (association == null)
                {
                    association = Associations.FirstOrDefault(i => field.To.IsAssignableFrom(i.From) && field.From.IsAssignableFrom(i.To));
                    isSourceToTargetOrder = false;
                }

                if (association == null)
                {
                    throw new Exception(string.Format("Jacaranda: unable to find an association from {0} to {1}, or vise versa.", field.From, field.To));
                }

                if (association.Instance != instance)
                {
                     association = new EntityAssociation() { From = association.From, To = association.To, Instance = instance };
                     Associations.Add(association);
                }


                field.Association = association;
                field.IsSourceToTargetOrder = isSourceToTargetOrder;

                associationFields.Add(field);
            }

            Type(type).AssociationFields = associationFields.ToArray();
        }

        private static void RegisterType(Type type)
        {
            EntityMetadata entity = new EntityMetadata() { Type = type };
            RegisteredTypes.Add(entity);

            types.Add(type, entity);


            object[] attributes = null;

            entity.Fields = type.GetProperties()
                .Where(i => (attributes = i.GetCustomAttributes(typeof(FieldAttribute), true)).Length > 0)
                .Select(i => new FieldMetadata()
                {
                    PropertyName = i.Name,
                    PropertyType = i.PropertyType,
                    Ignore = (attributes[0] as FieldAttribute).Ignore,
                    ExpressionName = (attributes[0] as FieldAttribute).ExpressionName,
                    FieldName = string.IsNullOrEmpty((attributes[0] as FieldAttribute).Name) ? i.Name : (attributes[0] as FieldAttribute).Name,
                    IsPrimaryKey = i.GetCustomAttributes(typeof(PrimaryKeyAttribute), true).Length > 0,
                    IsDbPrimaryKey = i.GetCustomAttributes(typeof(DbPrimaryKeyAttribute), true).Length > 0,
                    IsAutoIncrease = i.GetCustomAttributes(typeof(AutoIncreaseAttribute), true).Length > 0,
                    IsIndex = i.GetCustomAttributes(typeof(IndexAttribute), true).Length > 0,
                    IsGuid = i.GetCustomAttributes(typeof(IsGuidAttribute), true).Length > 0,
                    IsXml = (attributes[0] as FieldAttribute).IsXml,
                    DisplayName = ((attributes = i.GetCustomAttributes(typeof(DisplayAsAttribute), true)).Length > 0 && !string.IsNullOrEmpty((attributes[0] as DisplayAsAttribute).Name)) ? (attributes[0] as DisplayAsAttribute).Name : i.Name,
                    ValidationRules = ((attributes = i.GetCustomAttributes(typeof(ValidationRule), true)).Length > 0) ? attributes.Cast<ValidationRule>().OrderBy(j => j.Sequence).ToArray() : null,
                    Length = ((attributes = i.GetCustomAttributes(typeof(LengthAttribute), true)).Length > 0 && (attributes[0] as LengthAttribute).Length > 0) ? (attributes[0] as LengthAttribute).Length : (int?)null,
                    DefaultValue = ((attributes = i.GetCustomAttributes(typeof(DefaultAttribute), true)).Length > 0) ? (attributes[0] as DefaultAttribute).Value : null,
                })
                .ToArray();



            object[] froms = type.GetCustomAttributes(typeof(FromAttribute), false);
            foreach (FromAttribute a in froms)
            {
                EntityAssociation association = new EntityAssociation() { From = a.EntityType, To = type };
                if (Associations.Count(i => i.From.Equals(association.From) && i.To.Equals(association.To)) == 0)
                {
                    Associations.Add(association);
                }
            }

            object[] tos = type.GetCustomAttributes(typeof(ToAttribute), false);
            foreach (ToAttribute a in tos)
            {
                EntityAssociation association = new EntityAssociation() { From = type, To = a.EntityType };
                if (Associations.Count(i => i.From.Equals(association.From) && i.To.Equals(association.To)) == 0)
                {
                    Associations.Add(association);
                }
            }
        }
    }
}
